/*
 * Copyright 2002-2006 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.tigris.jasmincc.framework.implementation;

import java.io.*;
import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.tigris.jasmincc.configuration.ConfigurationManager;
import org.tigris.jasmincc.framework.*;
import org.tigris.jasmincc.framework.exception.ParserClassAlreadyExistedException;
import org.tigris.mappedlexer.io.CharOutputSequence;
import org.tigris.mappedlexer.io.implementation.StringCharOutputSequence;

/**
 * Represents as a JavaCC source to house information, which latter would be
 * print out to a .jcc or .jjt file.
 * 
 * @author zhisen.huang
 * 
 */
public class SimpleJavaCCModel implements JavaCCModel {

	protected static StringCharOutputSequence nothing = new StringCharOutputSequence();

	protected static final Log logger = LogFactory
			.getLog(SimpleJavaCCModel.class);

	protected List<RegexRegion> regexRegions = new ArrayList<RegexRegion>();

	protected String javaccSourceOutputDirectory = null;

	/**
	 * The grammar name.
	 */
	protected String grammarName;

	/**
	 * Java code
	 */
	protected List<String> javaBlocks = null;

	/**
	 * imported classes
	 */
	protected List<String> javaImports = null;

	protected List<String> skips = null;

	protected Properties options = null;

	protected Map<String, String> more = null;

	protected Properties tokens = null;

	protected List<CharOutputSequence> methods = null;

	protected List<CharOutputSequence> javacodes = null;

	protected CharOutputSequence parser = null;

	protected Properties specialTokens = null;

	protected Properties tokenManagerDeclaration = null;

	protected ConfigurationManager configurationHandler = null;

	protected boolean isJJTree = false;

	protected org.tigris.jasmincc.framework.DefaultParserHandler handler;

	public SimpleJavaCCModel() {
		javacodes = new ArrayList<CharOutputSequence>();

		methods = new ArrayList<CharOutputSequence>();

		javaImports = new ArrayList<String>();

		skips = new ArrayList<String>();

		options = new Properties();

		more = new HashMap<String, String>();

		tokens = new Properties();

		parser = null;

		specialTokens = new Properties();

		tokenManagerDeclaration = new Properties();
	}

	public void addJavaBlock(String javaBlock) {
		javaBlocks.add(javaBlock);
	}

	public void addJavaImport(String javaImport) {
		javaImports.add(javaImport);
	}

	public CharOutputSequence addMethod() {
		StringCharOutputSequence method = new StringCharOutputSequence();
		methods.add(method);
		return method;
	}

	public CharOutputSequence addJavaCode() {
		StringCharOutputSequence javacode = new StringCharOutputSequence();
		javacodes.add(javacode);
		return javacode;
	}

	public void addMore(String name, String value, String stmt) {
		more.put(name, value);
	}

	public void addOption(String name, String value) {
		if (!options.containsKey(name)) {
			options.put(name, value);
		}
	}

	public void addRegexRegion(RegexRegion rr) {
		regexRegions.add(rr);
	}

	public void addSkip(String skip) {
		if (!skips.contains(skip)) {
			skips.add(skip);
		}
	}

	public void addSpecialToken(String name, String value, String stmt) {
		specialTokens.put(name, value);
	}

	public void addToken(String name, String value, String stmt) {
		if (!tokens.containsKey(name) && !tokens.containsValue(value)) {
			tokens.put(name, value);
		}
	}

	public void addTokenManagerDeclaration(String name, String value,
			String stmt) {
		tokenManagerDeclaration.put(name, value);
	}

	/**
	 * @param printer
	 */
	private void emitOptions(PrintStream printer) {
		if (options.size() > 0) {
			printer.print("options {\n");
			for (Iterator<Object> iterator = options.keySet().iterator(); iterator
					.hasNext();) {
				String option = (String) iterator.next();
				if (option.startsWith("OUTPUT_DIRECTORY")) {
					continue;
				}
				printer.print(option + "=" + options.getProperty(option));
				printer.print(";");
				if (iterator.hasNext()) {
					printer.print("\n");
				}
			}
			printer.print("\n}\n");
		}
	}

	/**
	 * @param printer
	 * @throws Exception
	 */
	private void emitParser(PrintStream printer) throws Exception {
		if (parser == null) {
			// TODO get the class defined in the source file mentioned in the
			// configuration.
			// printDefaultParser(printer);
			logger.info(handler.getDefaultParser());
			printer.println(handler.getDefaultParser());
		} else {
			printer.print(parser.toString());
			printer.print("\n");
		}
	}

	/**
	 * @param printer
	 * @deprecated
	 */
	@Deprecated
	protected void emitSkips(PrintStream printer) {
		if (skips.size() > 0) {
			printer.print("SKIP : {\n");
			for (Iterator<String> iterator = skips.iterator(); iterator
					.hasNext();) {
				printer.print("\t");
				printer.print(iterator.next());
				if (iterator.hasNext()) {
					printer.print("\n\t|\n");
				}
			}
			printer.print("\n}\n");
		}
	}

	/**
	 * @param printer
	 * @deprecated
	 */
	@Deprecated
	private void emitTokens(PrintStream printer) {
		if (tokens.size() > 0) {
			printer.print("TOKEN : {\n");
			for (Iterator<Object> iterator = tokens.keySet().iterator(); iterator
					.hasNext();) {
				String key = (String) iterator.next();
				printer.print("\t<");
				printer.print(key);
				printer.print(":");
				printer.print(tokens.get(key));
				printer.print(">");
				if (iterator.hasNext()) {
					printer.print("\n\t|\n");
				}
			}
			printer.print("\n}\n");
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.googlecode.jasmincc.framework.JavaCCModel#getFileName()
	 */
	public String getFileName() {
		int dot = grammarName.lastIndexOf('.');
		String fileName = grammarName.substring(dot + 1, grammarName.length())
				+ (isJJTree ? ".jjt" : ".jj");
		if (this.javaccSourceOutputDirectory.endsWith(File.pathSeparator)) {
			return javaccSourceOutputDirectory + fileName;
		}
		return javaccSourceOutputDirectory + "\\" + fileName;
	}

	/**
	 * @return the javaBlocks
	 */
	public List<String> getJavaBlocks() {
		return javaBlocks;
	}

	/**
	 * @return the javaImports
	 */
	public List<String> getJavaImports() {
		return javaImports;
	}

	/**
	 * @return the methods
	 */
	public List<CharOutputSequence> getMethods() {
		return methods;
	}

	/**
	 * @return the options
	 */
	public Properties getOptions() {
		return options;
	}

	/**
	 * Return the parser class.
	 * 
	 * @return the parser class in the form of string
	 * 
	 */
	public CharOutputSequence getParserClass()
			throws ParserClassAlreadyExistedException {
		if (parser == null) {
			parser = new StringCharOutputSequence();
		} else {
			return nothing;
		}
		return parser;
	}

	/**
	 * @deprecated
	 */
	@Deprecated
	public List<String> getSkips() {
		return skips;
	}

	/**
	 * @return the specialTokens
	 * @deprecated
	 */
	@Deprecated
	public Properties getSpecialTokens() {
		return specialTokens;
	}

	/**
	 * @return the tokenManagerDeclaration
	 * @deprecated
	 */
	@Deprecated
	public Properties getTokenManagerDeclaration() {
		return tokenManagerDeclaration;
	}

	/**
	 * @deprecated
	 */
	@Deprecated
	public Properties getTokens() {
		return tokens;
	}

	/**
	 * Return if it is a JJTree.
	 * 
	 * @return true for JJTree
	 */
	public boolean isTree() {
		return isJJTree;
	}

	/**
	 * Render the code into an output stream.
	 * 
	 * @return the file of output
	 * 
	 */
	public JavaCCFile render() throws Exception {
		// StringBuffer buffer = new StringBuffer();
		JavaCCFile file = new JavaCCFile(getFileName());
		if (!file.exists()) {
			file.createNewFile();
		}
		FileOutputStream os = new FileOutputStream(file);
		PrintStream printer = new PrintStream(os);

		for (Iterator<Object> iterator = configurationHandler.getOptions()
				.keySet().iterator(); iterator.hasNext();) {
			String predefinedOptionKey = (String) iterator.next();
			if (!options.containsKey(predefinedOptionKey)) {
				options.put(predefinedOptionKey, configurationHandler
						.getOptions().get(predefinedOptionKey));
			}
		}
		emitOptions(printer);

		emitParser(printer);

		for (Iterator<Object> iterator = configurationHandler.getTokens()
				.keySet().iterator(); iterator.hasNext();) {
			String predefinedTokenKey = (String) iterator.next();
			if (!options.containsKey(predefinedTokenKey)) {
				tokens.put(predefinedTokenKey, configurationHandler.getTokens()
						.get(predefinedTokenKey));
			}
		}
		emitTokens(printer);

		emitRegexRegions(printer);

		if (methods.size() > 0) {
			for (Iterator<CharOutputSequence> iterator = methods.iterator(); iterator
					.hasNext();) {
				CharOutputSequence seq = iterator.next();
				printer.print(seq.toString());
				printer.append("\n");
			}
		}

		if (javacodes.size() > 0) {
			for (Iterator<CharOutputSequence> iterator = javacodes.iterator(); iterator
					.hasNext();) {
				CharOutputSequence seq = iterator.next();
				printer.print(seq.toString());
				// printer.append("\n");
			}
		}

		printer.close();
		os.close();

		return file;
	}

	/**
	 * @param configurationManager
	 *            the configurationManager to set
	 */
	public void setConfigurationHandler(
			ConfigurationManager configurationHandler) {
		this.configurationHandler = configurationHandler;
	}

	/**
	 * Set up the default parser handler
	 * 
	 * @param handler
	 *            the parser handler
	 * @see org.tigris.jasmincc.framework.DefaultParserHandler
	 */
	public void setDefaultParserHandler(
			org.tigris.jasmincc.framework.DefaultParserHandler handler) {
		this.handler = handler;
	}

	/**
	 * Set the grammar name.
	 * 
	 * @param grammarName
	 *            the grammar name
	 * 
	 */
	public void setGrammarName(String grammarName) {
		this.grammarName = grammarName;
	}

	/**
	 * Set up the directory where .jj and .jjt file to be created.
	 * 
	 * @param javaccSourceOutputDirectory
	 *            the javaccSourceOutputDirectory to set
	 */
	public void setJavaccSourceOutputDirectory(
			String javaccSourceOutputDirectory) {
		this.javaccSourceOutputDirectory = javaccSourceOutputDirectory;
	}

	/**
	 * Set the parser output.
	 * 
	 * @param parser
	 *            the parser output sequence
	 * @see org.tigris.mappedlexer.io.CharOutputSequence
	 */
	public void setParser(CharOutputSequence parser) {
		this.parser = parser;
	}

	public void setTree(boolean tree) {
		this.isJJTree = tree;
	}

	public void emitRegexRegions(PrintStream printer) {
		RegexSpec regexSpec = new RegexSpec();
		RegexRegion skipCollection = new RegexRegion();
		List<String> skip = new ArrayList<String>();
		List<RegexRegion> others = new ArrayList<RegexRegion>();

		skipCollection.setRegexRegionType("SKIP");
		for (RegexRegion rr : regexRegions) {
			if (rr.getRegexRegionType().equals("SKIP")) {
				for (RegexSpec rs : rr.getRegex()) {
					if (!skip.contains(rs.getName())) {
						skip.add(rs.getName());
						regexSpec = new RegexSpec();
						regexSpec.setName(rs.getName());
						skipCollection.addRegexSpec(regexSpec);
					}
				}
			} else {
				others.add(rr);
			}
		}
		for (String predefinedSkipKey : configurationHandler.getSkips()) {
			predefinedSkipKey = "\"" + predefinedSkipKey + "\"";
			if (!skip.contains(predefinedSkipKey)) {
				skip.add(predefinedSkipKey);
				regexSpec = new RegexSpec();
				regexSpec.setName(predefinedSkipKey);
				skipCollection.addRegexSpec(regexSpec);
			}
		}
		printer.print(skipCollection);

		for (RegexRegion rr : others) {
			printer.print(rr.toString());
		}
	}
}
